package org.minidao.aop;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.minidao.annotation.Arguments;
import org.minidao.annotation.ResultType;
import org.minidao.annotation.Sql;
import org.minidao.aspect.EmptyInterceptor;
import org.minidao.def.MiniDaoConstants;
import org.minidao.pojo.MiniDaoPage;
import org.minidao.spring.rowMapper.MiniColumnMapRowMapper;
import org.minidao.spring.rowMapper.MiniColumnOriginalMapRowMapper;
import org.minidao.util.FreemarkerParseFactory;
import org.minidao.util.MiniDaoUtil;
import org.minidao.util.ParameterNameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import ognl.Ognl;
import ognl.OgnlException;



/**
* @author 库琦
* @version 2018年1月7日 下午4:41:26
* 
*/

public class MiniDaoHandler implements InvocationHandler {
	private static final Logger logger = Logger.getLogger(MiniDaoHandler.class);
	@Autowired
	private JdbcTemplate jdbcTemplate;
	@Autowired
	private NamedParameterJdbcTemplate namedparameterjdbctemplate;
	private String UPPETR_KEY ="upper";
	private String LOWER_KEY= "lower";
	private String keyType="origin";
	private boolean formatSql=false;
	private boolean showSql=false;
	private String dbType;
	private EmptyInterceptor emptyInterceptor;

	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
	public String getKeyType() {
		return keyType;
	}
	public void setKeyType(String keyType) {
		this.keyType = keyType;
	}
	public boolean isFormatSql() {
		return formatSql;
	}
	public void setFormatSql(boolean formatSql) {
		this.formatSql = formatSql;
	}
	public boolean isShowSql() {
		return showSql;
	}
	public void setShowSql(boolean showSql) {
		this.showSql = showSql;
	}
	public String getDbType() {
		return dbType;
	}
	public void setDbType(String dbType) {
		this.dbType = dbType;
	}
	public EmptyInterceptor getEmptyInterceptor() {
		return emptyInterceptor;
	}
	public void setEmptyInterceptor(EmptyInterceptor emptyInterceptor) {
		this.emptyInterceptor = emptyInterceptor;
	}
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	//返回結果
		Object returnObj = null;
		//SQL模板參數
		String templateSql = null;
		//sql模板参数
		Map<String, Object> sqlParamsMap = new HashMap<String,Object>();
		//分页参数，调用自己封装类。
		    MiniDaoPage pageSetting = new MiniDaoPage();
		    //装载SQL模板所需参数
		    templateSql = installDaoMetaData(pageSetting,method,sqlParamsMap,args);
		    //解析SQL模板，返回可执行SQL
		    String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);
		     //组装SQL占位符参数
		    Map<String, Object> sqlMap = installPlaceholderSqlParam(executeSql, sqlParamsMap);
		    //获取SQL执行返回值
		    try {
		    	 returnObj = getReturnMinidaoResult(templateSql, pageSetting, method, executeSql, sqlMap);
				    
			} catch (Exception e) {
				returnObj = null;
				if(e instanceof EmptyResultDataAccessException){
					//数据查询为空，不抛出Spring异常
				}else{
					e.printStackTrace();
					throw e;
				}
			}
		    if (showSql) {
				System.out.println("MiniDao-SQL:\n\n" + executeSql);
				logger.info("MiniDao-SQL:\n\n" + executeSql);
			}
		   
		return returnObj;
	}
	private Map<String,Object> installPlaceholderSqlParam(String executeSql,Map sqlParamsMap) throws OgnlException{
		Map<String, Object> map = new HashMap<String, Object>();
	
		String regEx = ":[ tnx0Bfr]*[0-9a-z.A-Z_]+"; // 表示以：开头，[0-9或者.或者A-Z大小都写]的任意字符，超过一个
		
		Pattern pat = Pattern.compile(regEx);
		Matcher m = pat.matcher(executeSql);
		while (m.find()) {
			logger.debug(" Match [" + m.group() + "] at positions " + m.start() + "-" + (m.end() - 1));
			String ognl_key = m.group().replace(":", "").trim();
			logger.debug(" --- minidao --- 解析参数 --- " + ognl_key);
			map.put(ognl_key, Ognl.getValue(ognl_key, sqlParamsMap));
		}
		return map;
		                  
		}
	private static boolean checkActiveKey(String methodName){
		String keys[] =MiniDaoConstants.INF_METHOD_ACTIVE.split(",");
		for(String s:keys){
			if(methodName.startsWith(s)){
				return true;
			}
		}
		return false;
	}
	/**
	 * 判斷SQL是否（非查詢）
	 * 
	 * @param methodName
	 * @return
	 */
	private static boolean checkActiveSql(String sql) {
		sql = sql.trim().toLowerCase();
		String keys[] = MiniDaoConstants.INF_METHOD_ACTIVE.split(",");
		for (String s : keys) {
			if (sql.startsWith(s))
				return true;
		}
		return false;
	}
	/**
	 * 判斷是否批處理
	 * 
	 * @param methodName
	 * @return
	 */
	private static boolean checkBatchKey(String methodName) {
		String keys[] = MiniDaoConstants.INF_METHOD_BATCH.split(",");
		for (String s : keys) {
			if (methodName.startsWith(s))
				return true;
		}
		return false;
	}
	/**
	 * 把批量处理的结果拼接起来
	 * 
	 * @Author JueYue
	 * @date 2013-11-17
	 */
	private void addResulArray(int[] result, int index, int[] arr) {
		int length = arr.length;
		for (int i = 0; i < length; i++) {
			result[index - length + i] = arr[i];
		}
	}
	/**
	 * 批处理
	 * 
	 * @Author JueYue
	 * @date 2013-11-17
	 * @return
	 */
	private int[] batchUpdate(String executeSql) {
		String[] sqls = executeSql.split(";");
		if (sqls.length < 100) {
			return jdbcTemplate.batchUpdate(sqls);
		}
		int[] result = new int[sqls.length];
		List<String> sqlList = new ArrayList<String>();
		for (int i = 0; i < sqls.length; i++) {
			sqlList.add(sqls[i]);
			if (i % 100 == 0) {
				addResulArray(result, i + 1, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
				sqlList.clear();
			}
		}
		addResulArray(result, sqls.length, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
		return result;
	}
	/**
	 * 获取MiniDao处理结果集
	 * 
	 * @param dbType
	 * @param pageSetting
	 * @param jdbcTemplate
	 * @param method
	 * @param executeSql
	 * @return 结果集
	 */
	private Object getReturnMinidaoResult(String dbType, MiniDaoPage pageSetting, Method method, String executeSql, Map<String, Object> paramMap) {
		//调用SpringJdbc引擎，执行SQL返回值。
		//1.获取返回值类型
		String methodName = method.getName();
		//判断是否非查询方法。
		if(checkActiveKey(methodName)||checkActiveSql(executeSql)){
			if(paramMap!=null){
				return namedparameterjdbctemplate.update(executeSql, paramMap);
			}else{
				return jdbcTemplate.update(executeSql);
			}
		}else if(checkBatchKey(methodName)){
			return batchUpdate(executeSql);
		}else{
			//如果是查询方法。
			Class<?> returnType = method.getReturnType();
			if(returnType.isPrimitive()){
				Number number= namedparameterjdbctemplate.queryForObject(executeSql, paramMap, BigDecimal.class);
				if("int".equals(number)){
					return number.intValue();
				}
				else if("double".equals(number)){
					return number.doubleValue();
				}else if ("long".equals(returnType.getCanonicalName())) {
					return number.longValue();
				
			}else if(returnType.isAssignableFrom(List.class)||returnType.isAssignableFrom(MiniDaoPage.class)){
			int page = pageSetting.getPage();
			int rows = pageSetting.getRows();
			if(rows!=0&&page!=0){
				if(returnType.isAssignableFrom(MiniDaoPage.class)){
					if(paramMap!=null){
						pageSetting.setTotal(namedparameterjdbctemplate.queryForObject(getCountSql(executeSql), paramMap, Integer.class));
					}else{
						pageSetting.setPage(jdbcTemplate.queryForObject(getCountSql(executeSql), Integer.class));
					}
				}
					 executeSql = MiniDaoUtil.createPageSql(dbType, executeSql, page, rows);
				
			}
			
			     RowMapper resultType = getListRealType(method); 
			     List list;
			     if(paramMap!=null){
			    	 return namedparameterjdbctemplate.queryForObject(executeSql, paramMap, getColumnMapRowMapper());
			     }else{
			    	 list = jdbcTemplate.query(executeSql, resultType);
			     }
			     if(returnType.isAssignableFrom(MiniDaoPage.class)){
			    	 //
			    	 pageSetting.setResults(list);
			     }else{
			    	 return list;
			    	 
			     }
			     
			}else if(returnType.isAssignableFrom(Map.class)){
				if(paramMap !=null){
					return namedparameterjdbctemplate.queryForObject(executeSql, paramMap, getColumnMapRowMapper());
				}else {
					jdbcTemplate.queryForObject(executeSql, getColumnMapRowMapper());
				}
			}else if(returnType.isAssignableFrom(String.class)){
				if(paramMap!=null){
					return namedparameterjdbctemplate.queryForObject(executeSql, paramMap, String.class);
				}else{
					return jdbcTemplate.queryForObject(executeSql, returnType);
				}
				
			}else{
				
				 RowMapper<?> rm = BeanPropertyRowMapper.newInstance(returnType);
				 if(paramMap!=null){
					 return namedparameterjdbctemplate.queryForObject(executeSql, paramMap, rm);
				 }else{
					 return jdbcTemplate.queryForObject(executeSql, rm);
				 }
				
			}
			}
		}
		
		
		
		return null;
		
		
		
	}
	/*
	*
	 * 根据参数设置map的key大小写
	 **/
	private RowMapper<Map<String, Object>> getColumnMapRowMapper() {
		if(getKeyType().equalsIgnoreCase(LOWER_KEY)){
			return new MiniColumnMapRowMapper();
			}else if(getKeyType().equalsIgnoreCase(UPPETR_KEY)){
				return  new ColumnMapRowMapper();
			}else{
				return new MiniColumnOriginalMapRowMapper();
			}
	}
	private RowMapper<?> getListRealType(Method method){
		ResultType resultType = method.getAnnotation(ResultType.class);
		if(resultType.value().equals(Map.class)){
			return getColumnMapRowMapper();
		}
		return BeanPropertyRowMapper.newInstance(resultType.value());
		}
	private String getCountSql(String sql){
		sql=removeOrderBy(sql);
		return "select count(0) from ("+sql+") temp_count";
	}
	//去除子查询中的order by。
	public String removeOrderBy(String sql){
		if(sql == null){
			return null;
		}
		sql = sql.replaceAll("(?i)order by [\\s|\\S]+$", "");  
		   return sql;  
	}
	/**
	 * 装载SQL模板参数
	 * 
	 * @param pageSetting
	 * 
	 * @param method
	 * @param sqlParamsMap
	 *            返回(装载模板参数)
	 * @param args
	 * @return templateSql(@SQL标签的SQL)
	 * @throws Exception
	 */
	@SuppressWarnings("unused")
	private String installDaoMetaData(MiniDaoPage pageSetting,Method method,Map<String,Object> sqlParamsMap, Object[] args) throws Exception{
		if(emptyInterceptor!=null&&args!=null && args.length==1){
			String methodName = method.getName();
			Object obj = args[0];
			Field[] fields = obj.getClass().getDeclaredFields();
			if(methodName.startsWith("insert")){
				if(emptyInterceptor!=null){
					emptyInterceptor.onInsert(fields, obj);
				}
			}
			if(methodName.startsWith("update")){
				emptyInterceptor.onUpdate(fields, obj);
			}
		}
		String templateSql=null;
		//如果方法参数大于1个的话，方法必须使用注释标签Arguments
		boolean arguments_flag = method.isAnnotationPresent(Arguments.class);
		if(arguments_flag){
			//[1].获取方法的参数标签。
			Arguments arguments = method.getAnnotation(Arguments.class);
			if(arguments.value().length!=args.length){
				throw new Exception("注释标签@Argument参数数目，与方法参数数目不相等");
			}
			int args_num = 0;
			//将args装换成键值对，封装成Map对象。
			for(String v:arguments.value()){
				if(v.equalsIgnoreCase("page")){
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
				}
				if(v.equalsIgnoreCase("row")){
					pageSetting.setRows(Integer.parseInt(args[args_num].toString()));
				}
				sqlParamsMap.put(v, args[args_num]);
				args_num++;
			}
		}else{
			if(args!=null&&args.length>=1){
				String[] params = ParameterNameUtils.getMethodParameterNamesByAnnotation(method);
			if(params==null || params.length==0){
				throw new Exception("方法参数数目>=2,必须使用：方法标签@argrument或参数标签@param");
			}
			if(params.length!=args.length){
				throw new Exception("方法参数数目>=2，参数必须使用：标签@param");
				
			}
			int args_num = 0;
			for(String v: params){
				if(v == null){
					throw new Exception("Dao 接口定义，所有参数必须使用@param标签");	
				}
				if(v.equalsIgnoreCase("page")){
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
				}
				if(v.equalsIgnoreCase("rows")){
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
				}
				sqlParamsMap.put(v, args[args_num]);
				args_num++;
			}
			}else if(args !=null && args.length==1){
				sqlParamsMap.put(MiniDaoConstants.SQL_FTL_DTO, args[0]);
			}
			
		}
		//获取方法的Sql标签
		if(method.isAnnotationPresent(Sql.class)){
			Sql sql = method.getAnnotation(Sql.class);
			//如果用户自定义标签SQL,则Sql文件无效。
			if(StringUtils.isNotEmpty(sql.value())){
				templateSql = sql.value();
			}
			logger.debug("@Sql----------------------------"+sql.value());
		}
		return templateSql;
		
		}
	private String parseSqlTemplate(Method method,String templateSql,Map<String,Object> sqlParamsMap){
		//根据命名规范[接口名_方法名.sql],获取SQL模板文件路径。
		String executeSql =null;
		//获取SQL模板内容
		//通过模板引擎给SQL模板装载参数，解析生成可执行SQL
		if(StringUtils.isNotEmpty(templateSql)){
			executeSql = FreemarkerParseFactory.parseTemolateContent(templateSql, sqlParamsMap);		
	
		}else{
			String sqlTemplatePath = method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/")+"_"+method.getName()+".sql";
			if(!FreemarkerParseFactory.isExistTemploate(sqlTemplatePath)){
				sqlTemplatePath = method.getDeclaringClass().getName().replace(".", "/")+"_"+method.getName()+".sql";
			}
			executeSql = FreemarkerParseFactory.parseTemplate(sqlTemplatePath, sqlParamsMap);
		}
		return executeSql;
		
	}
}
